package source;

/*
This file is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License version 2, 1991 as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details.

A copy of the GNU General Public License can be found at:
http://www.gnu.org/licenses/gpl.html
*/
import java.io.*;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class PlayerHandler {

	// Remark: the player structures are just a temporary solution for now
	// Later we will avoid looping through all the players for each player
	// by making use of a hash table maybe based on map regions (8x8 granularity should be ok)
	public static final int maxPlayers = 512;
	public static Player players[] = new Player[maxPlayers];
	public static int playerSlotSearchStart = 1;			// where we start searching at when adding a new player
	public static String kickNick = "", IPBanNick = "", BanNick = "";
	public static boolean kickAllPlayers = false;
	public static String messageToAll = "";
	public static int playerCount=0;
	public static String playersCurrentlyOn[] = new String[maxPlayers];
	public static boolean updateAnnounced;
	public static boolean updateRunning;
	public static int updateSeconds;
	public static long updateStartTime;

    static {
        for (int i = 0; i < maxPlayers; i++) {
            players[i] = null;
        }
    }

	// a new client connected
	public static void newPlayerClient(java.net.Socket s, String connectedFrom) {
		// first, search for a free slot
		//int slot = -1, i = playerSlotSearchStart;
		int slot = -1, i = 1;
		do {
			if(players[i] == null) {
				slot = i;
				break;
			}
			i++;
			if(i >= maxPlayers) i = 0;		// wrap around
//		} while(i != playerSlotSearchStart);
		} while(i <= maxPlayers);

		client newClient = new client(s, slot);
		(new Thread(newClient)).start();
		if(slot == -1) return;		// no free slot found - world is full
		players[slot] = newClient;
		players[slot].connectedFrom = connectedFrom;

		// start at next slot when issuing the next search to speed it up
		playerSlotSearchStart = slot + 1;
		if(playerSlotSearchStart > maxPlayers) playerSlotSearchStart = 1;
		// Note that we don't use slot 0 because playerId zero does not function
		// properly with the client.
	}

	public static void destruct() {
		for(int i = 0; i < maxPlayers; i++) {
			if(players[i] == null) continue;
			players[i].destruct();
			players[i] = null;
		}
	}

	public static int getPlayerCount() {
		return playerCount;
	}

	public static void updatePlayerNames(){
		playerCount=0;
		for(int i = 0; i < maxPlayers; i++) {
			if(players[i] != null) {
				playersCurrentlyOn[i] = players[i].playerName;
				playerCount++;
			}
			else
				playersCurrentlyOn[i] = "";
		}
	}

	public static boolean isPlayerOn(String playerName) {
		for(int i = 0; i < maxPlayers; i++) {
			if(playersCurrentlyOn[i] != null){
				if(playersCurrentlyOn[i].equalsIgnoreCase(playerName)) return true;
			}
		}
		return false;
	}
	public static void process() {
		updatePlayerNames();

		if (messageToAll.length() > 0) {
			for (int i = 0; i < maxPlayers; i++){
				if (players[i] != null){
					client p = (client)players[i];
					p.sendMessage(messageToAll);
				}
			}
			messageToAll="";
		}
		if (kickAllPlayers) {
			for (int i = 0; i < maxPlayers; i++){
				if (players[i] != null){
					client p = (client)players[i];
					p.disconnected = true;
					p.logout();
				}
			}
			kickAllPlayers = false;
		}

		// at first, parse all the incoming data
		// this has to be seperated from the outgoing part because we have to keep all the player data
		// static, so each client gets exactly the same and not the one clients are ahead in time
		// than the other ones.
		for(int i = 0; i < maxPlayers; i++) {
			if(players[i] == null || !players[i].isActive) continue;

			players[i].actionAmount--;

			players[i].preProcessing();
			while(players[i].process());
			players[i].postProcessing();

			players[i].getNextPlayerMovement();

			if(players[i].playerName.equalsIgnoreCase(kickNick)){
				client p = (client)players[i];
				p.disconnected = true;
				p.logout();
			}else if (players[i].playerName.equalsIgnoreCase(IPBanNick)){
				client p = (client)players[i];
				p.disconnected = true;
				p.logout();
				IPBanNick="";
				BufferedWriter ban = null;
				try{
					ban = new BufferedWriter(new FileWriter(".config/banned.cfg", true));
					ban.write(players[i].connectedFrom);
					BanHandler.Banned[BanHandler.BannedCount] = players[i].connectedFrom;
					ban.newLine();
					ban.close();
				}catch (IOException ioexception) {
					misc.println(players[i].playerName+": error writing IP ban file.");
				}
			}else if (players[i].playerName.equalsIgnoreCase(BanNick)){
				client p = (client)players[i];
				p.disconnected = true;
				p.logout();
				BanNick="";
				BufferedWriter ban = null;
				try{
					ban = new BufferedWriter(new FileWriter(".config/banned.cfg", true));
					ban.write(players[i].playerName);
					BanHandler.Banned[BanHandler.BannedCount] = players[i].playerName;
					ban.newLine();
					ban.close();
				}catch (IOException ioexception) {
					misc.println(players[i].playerName+": error writing ban file.");
				}
			}
			
			if(players[i].disconnected && players[i] != null) {
				if (players[i].savefile == true) {
					if(saveGame(players[i])){
						misc.println("Game saved for player "+players[i].playerName);
					} else {
						misc.println("Could not save for "+players[i].playerName);
					};
				} else {
					misc.println("Did not save for "+players[i].playerName);
				}
				removePlayer(players[i]);
				players[i] = null;
			}
		}

		// loop through all players and do the updating stuff
		for(int i = 0; i < maxPlayers; i++) {
			if(players[i] == null || !players[i].isActive) continue;

			Calendar cal = new GregorianCalendar();
			int day = cal.get(Calendar.DAY_OF_MONTH);
			int month = cal.get(Calendar.MONTH);
			int year = cal.get(Calendar.YEAR);
			int calc = ((year * 10000) + (month * 100) + day);
			players[i].playerLastLogin = calc;
			if(players[i].disconnected) {
				if (players[i].savefile == true) {
					if(saveGame(players[i])) {
						misc.println("Game saved for player "+players[i].playerName);
					} else {
						misc.println("Could not save for "+players[i].playerName);
					};
				} else {
					misc.println("Did not save for "+players[i].playerName);
				}
				removePlayer(players[i]);
				players[i] = null;
			} else {
				if(!players[i].initialized) {
					players[i].initialize();
					players[i].initialized = true;
				} else {
					players[i].update();
				}
			}
		}

		if (updateRunning && !updateAnnounced) {
			updateAnnounced = true;
		}

		if (updateRunning && System.currentTimeMillis() - updateStartTime > (updateSeconds*1000)) {
			kickAllPlayers = true;
			server.ShutDown = true;
		}

		// post processing
		for(int i = 0; i < maxPlayers; i++) {
			if(players[i] == null || !players[i].isActive) continue;

			players[i].clearUpdateFlags();
		}
	}

	public static void updateNPC(Player plr, stream str) {
		updateBlock.currentOffset = 0;
		
		str.createFrameVarSizeWord(65);
		str.initBitAccess();
		
		str.writeBits(8, plr.npcListSize);
		int size = plr.npcListSize;
		plr.npcListSize = 0;
		for(int i = 0; i < size; i++) {
			if(plr.RebuildNPCList == false && plr.withinDistance(plr.npcList[i]) == true) {
				plr.npcList[i].updateNPCMovement(str);
				plr.npcList[i].appendNPCUpdateBlock(updateBlock);
				plr.npcList[plr.npcListSize++] = plr.npcList[i];
			} else {
				int id = plr.npcList[i].npcId;
				plr.npcInListBitmap[id>>3] &= ~(1 << (id&7));		// clear the flag
				str.writeBits(1, 1);
				str.writeBits(2, 3);		// tells client to remove this npc from list
			}
		}

		// iterate through all npcs to check whether there's new npcs to add
		for(int i = 0; i < NPCHandler.maxNPCs; i++) {
			if(NPCHandler.npcs[i] != null) {
				int id = NPCHandler.npcs[i].npcId;
				if (plr.RebuildNPCList == false && (plr.npcInListBitmap[id>>3]&(1 << (id&7))) != 0) {
					// npc already in npcList
				} else if (plr.withinDistance(NPCHandler.npcs[i]) == false) {
					// out of sight
				} else {
					plr.addNewNPC(NPCHandler.npcs[i], str, updateBlock);
				}
			}
		}
		
		plr.RebuildNPCList = false;

		if(updateBlock.currentOffset > 0) {
			str.writeBits(14, 16383);	// magic EOF - needed only when npc updateblock follows
			str.finishBitAccess();

			// append update block
			str.writeBytes(updateBlock.buffer, updateBlock.currentOffset, 0);
		} else {
			str.finishBitAccess();
		}
		str.endFrameVarSizeWord();
	}

	private static stream updateBlock = new stream(new byte[10000]);
	// should actually be moved to client.java because it's very client specific
	public static void updatePlayer(Player plr, stream str) {
		updateBlock.currentOffset = 0;
		
		if (updateRunning && !updateAnnounced) {
			str.createFrame(114);
			str.writeWordBigEndian(updateSeconds*50/30);
		}

		// update thisPlayer
		plr.updateThisPlayerMovement(str);		// handles walking/running and teleporting
		// do NOT send chat text back to thisPlayer!
		boolean saveChatTextUpdate = plr.chatTextUpdateRequired;
		plr.chatTextUpdateRequired = false;
		plr.appendPlayerUpdateBlock(updateBlock);
		plr.chatTextUpdateRequired = saveChatTextUpdate;

		str.writeBits(8, plr.playerListSize);
		int size = plr.playerListSize;

		// update/remove players that are already in the playerList
		plr.playerListSize = 0;		// we're going to rebuild the list right away
		for(int i = 0; i < size; i++) {
			// this update packet does not support teleporting of other players directly
			// instead we're going to remove this player here and readd it right away below
			if(plr.didTeleport == false && plr.withinDistance(plr.playerList[i]) == true) {
				plr.playerList[i].updatePlayerMovement(str);
				plr.playerList[i].appendPlayerUpdateBlock(updateBlock);
				plr.playerList[plr.playerListSize++] = plr.playerList[i];
			} else {
				int id = plr.playerList[i].playerId;
				plr.playerInListBitmap[id>>3] &= ~(1 << (id&7));		// clear the flag
				str.writeBits(1, 1);
				str.writeBits(2, 3);		// tells client to remove this char from list
			}
		}

		// iterate through all players to check whether there's new players to add
		for(int i = 0; i < maxPlayers; i++) {
			if(players[i] == null || players[i].isActive == false || players[i] == plr) {
				//not existing, not active or you are that player
			} else {
				int id = players[i].playerId;
				if(plr.didTeleport == false && (plr.playerInListBitmap[id>>3]&(1 << (id&7))) != 0) {
					// player already in playerList
				} else if(plr.withinDistance(players[i]) == false) {
					// out of sight
				} else {
					plr.addNewPlayer(players[i], str, updateBlock);
				}
			}
		}

		if(updateBlock.currentOffset > 0) {
			str.writeBits(11, 2047);	// magic EOF - needed only when player updateblock follows
			str.finishBitAccess();

			// append update block
			str.writeBytes(updateBlock.buffer, updateBlock.currentOffset, 0);
		} else {
			str.finishBitAccess();
		}
		str.endFrameVarSizeWord();
	}

	public static int lastchatid = 1; //PM System

	private static void removePlayer(Player plr) {
		if(plr.Privatechat != 2) { //PM System
			for(int i = 1; i < maxPlayers; i++) {
				if (players[i] == null || players[i].isActive == false) continue;
				players[i].pmupdate(plr.playerId, 0);
			}
		}
		for (int i = 0; i < NPCHandler.maxNPCs; i++){
			if (NPCHandler.npcs[i] != null){
				if (NPCHandler.npcs[i].followId == plr.playerId){
					NPCHandler.npcs[i] = null;
					NPCHandler.npcs[i].absX = 0;
					NPCHandler.npcs[i].absY = 0;
				}
			}
		}
		// anything can be done here like unlinking this player structure from any of the other existing structures
		plr.destruct();
	}

	public static boolean saveGame(Player plr) {
		BufferedWriter characterfile = null;
		try {
			characterfile = new BufferedWriter(new FileWriter("./characters/"+plr.playerName+".txt"));
/*ACCOUNT*/
			characterfile.write("[ACCOUNT]");
			characterfile.newLine();
			characterfile.write("character-username = ");
			characterfile.write(plr.playerName);
			characterfile.newLine();
			characterfile.write("character-password = ");
			characterfile.write(plr.playerPass);
			characterfile.newLine();
			characterfile.newLine();
/*CHARACTER*/
			characterfile.write("[CHARACTER]");
			characterfile.newLine();
			characterfile.write("character-height = ");
			characterfile.write(Integer.toString(plr.heightLevel));
			characterfile.newLine();
			characterfile.write("character-posx = ");
			characterfile.write(Integer.toString(plr.absX));
			characterfile.newLine();
			characterfile.write("character-posy = ");
			characterfile.write(Integer.toString(plr.absY));
			characterfile.newLine();
			characterfile.write("character-rights = ");
			characterfile.write(Integer.toString(plr.playerRights));
			characterfile.newLine();
			characterfile.write("character-ismember = ");
			characterfile.write(Integer.toString(plr.playerIsMember));
			characterfile.newLine();
			characterfile.write("character-messages = ");
			characterfile.write(Integer.toString(plr.playerMessages));
			characterfile.newLine();
			characterfile.write("character-lastconnection = ");
			characterfile.write(plr.playerLastConnect);
			characterfile.newLine();
			characterfile.write("character-lastlogin = ");
			characterfile.write(Integer.toString(plr.playerLastLogin));
			characterfile.newLine();
			characterfile.write("character-energy = ");
			characterfile.write(Integer.toString(plr.playerEnergy));
			characterfile.newLine();
			characterfile.write("character-gametime = ");
			characterfile.write(Integer.toString(plr.playerGameTime));
			characterfile.newLine();
			characterfile.write("character-gamecount = ");
			characterfile.write(Integer.toString(plr.playerGameCount));
			characterfile.newLine();
			characterfile.write("character-brightness = ");
			characterfile.write(Integer.toString(plr.playerBrightness));
			characterfile.newLine();
			characterfile.newLine();
/*EQUIPMENT*/
			characterfile.write("[EQUIPMENT]");
			characterfile.newLine();
			for (int i = 0; i < plr.playerEquipment.length; i++) {
				characterfile.write("character-equip = ");
				characterfile.write(Integer.toString(i));
				characterfile.write("	", 0, 1);
				characterfile.write(Integer.toString(plr.playerEquipment[i]));
				characterfile.write("	", 0, 1);
				characterfile.write(Integer.toString(plr.playerEquipmentN[i]));
				characterfile.write("	", 0, 1);
				characterfile.newLine();
			}
			characterfile.newLine();
/*LOOK*/
			characterfile.write("[LOOK]");
			characterfile.newLine();
			for (int i = 0; i < plr.playerLook.length; i++) {
				characterfile.write("character-look = ");
				characterfile.write(Integer.toString(i));
				characterfile.write("	");
				characterfile.write(Integer.toString(plr.playerLook[i]));
				characterfile.newLine();
			}
			characterfile.newLine();
/*SKILLS*/
			characterfile.write("[SKILLS]");
			characterfile.newLine();
			for (int i = 0; i < plr.playerLevel.length; i++) {
				characterfile.write("character-skill = ");
				characterfile.write(Integer.toString(i));
				characterfile.write("	");
				characterfile.write(Integer.toString(plr.playerLevel[i]));
				characterfile.write("	");
				characterfile.write(Double.toString(plr.playerXP[i]).replaceAll(".0",""));
				characterfile.newLine();
			}
			characterfile.newLine();
/*ITEMS*/
			characterfile.write("[ITEMS]");
			characterfile.newLine();
			for (int i = 0; i < plr.playerItems.length; i++) {
				if (plr.playerItems[i] > 0) {
					characterfile.write("character-item = ");
					characterfile.write(Integer.toString(i));
					characterfile.write("	");
					characterfile.write(Integer.toString(plr.playerItems[i]));
					characterfile.write("	");
					characterfile.write(Integer.toString(plr.playerItemsN[i]));
					characterfile.newLine();
				}
			}
			characterfile.newLine();
/*BANK*/
			characterfile.write("[BANK]");
			characterfile.newLine();
			for (int i = 0; i < plr.bankItems.length; i++) {
				if (plr.bankItems[i] > 0) {
					characterfile.write("character-bank = ");
					characterfile.write(Integer.toString(i));
					characterfile.write("	");
					characterfile.write(Integer.toString(plr.bankItems[i]));
					characterfile.write("	");
					characterfile.write(Integer.toString(plr.bankItemsN[i]));
					characterfile.newLine();
				}
			}
			characterfile.newLine();
/*FRIENDS*/
			characterfile.write("[FRIENDS]");
			characterfile.newLine();
			for (int i = 0; i < plr.friends.length; i++) {
				if (plr.friends[i] != null) {
					characterfile.write("character-friend = ");
					characterfile.write(Integer.toString(i));
					characterfile.write("	");
					characterfile.write(plr.friends[i]);
					characterfile.newLine();
				}
			}
			characterfile.newLine();
/*IGNORES*/
			characterfile.write("[IGNORES]");
			characterfile.newLine();
			for (int i = 0; i < plr.ignores.length; i++) {
				if (plr.ignores[i] != null) {
					characterfile.write("character-ignore = ");
					characterfile.write(Integer.toString(i));
					characterfile.write("	");
					characterfile.write(plr.ignores[i]);
					characterfile.newLine();
				}
			}
			characterfile.newLine();
/*EOF*/
			characterfile.write("[EOF]");
			characterfile.newLine();
			characterfile.newLine();
			characterfile.close();
		} catch(IOException ioexception) {
			misc.println(plr.playerName+": error writing file.");
			return false;
		}
		return true;
	}
}
